package Http;
use strict;
use warnings;
use utf8;

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(
HttpCreate
);

use EV;
use IO::Socket::INET;
#use IO::Socket::IP;
use JSON::XS;
use HTTP::Parser::XS qw(parse_http_request);
use Data::Dumper;

my $parse_error = {
	413 => "too long message",
	400 => "broken message",
};

use Util;
use Command qw(ProcessCommand);

my $con_count = 1;
my %con_all;



sub HttpCreate {
	my ($addr,$port) = @_;
	my $http = new IO::Socket::INET(
	#my $http = new IO::Socket::IP(
			Listen => 100,
			LocalHost =>  $addr,
			LocalPort =>  $port,
			ReuseAddr => 1,
			Blocking => 0,
		);
	unless($http){
		print "HTTP $@\n";
		return undef;
	}
	# слушаем основной сокет
	return EV::io $http, EV::READ, \&stream_http_handler;
}

sub stream_http_handler {
	# принимаем подключение
    my ($w, $r) = @_;
    my $new = $w->fh->accept;
    unless($new){
		if($main::params{debug}){
			print "Cannot accept '$!' '$@'\n";
		}
		return 0;
    }else{
		if($main::params{debug}){
			printf "Accepted from %s:%d\n",$new->peerhost, $new->peerport;
		}
    }
	$new->blocking(0);
    my $nw = EV::io $new, EV::READ, \&read_http_handler;
	## сохраняем слушателя, буфер с принятыми данными

	$con_all{++$con_count} = $nw;
	$nw->data([$con_count,"",$new->peerhost]);
}
sub read_http_handler {
	# Получили данные
	my ($w, $r) = @_;
	my $buf;
	my $fh = $w->fh;
	if($fh->opened){
		$fh->recv($buf,1024);
	}
	if($buf){
		# есть данные, цепляем к предыдущим если есть и пытаемся обработать
		$w->data->[1] .= $buf;
		my $cmd = HttpParse(\$w->data->[1],$w->data->[2]);
		if($cmd){
			my $ret;
			if(ref $cmd){
				# готовое сообщение для обработки
				$ret = ProcessCommand($cmd,$fh,\&send_http_message);
			}else{
				# ошибка парсинга
				$ret = {error=>$cmd,text=>$parse_error->{$cmd}};
			}
			send_http_message($fh,$ret) if $ret;
		}else{
			return 0;
		}
	}
	delete $con_all{$w->data->[0]};
}

# return false if need more data
# return true if all done and not need keep handler
sub HttpParse {
	my ($rstr,$rip) = @_;
	if(length($$rstr) > 4096){
		# очень длинные запросы не обрабатываем
		return 413;
	}
	my $param = {};
	my $header_size = parse_http_request($$rstr,$param);
	if($header_size == -2){
		# неполное сообщение
		return undef;
	}elsif($header_size == -1){
		# поломанное сообщение
		return 400;
	}
	my $body = substr($$rstr,$header_size);

	if($param->{CONTENT_LENGTH} && $param->{CONTENT_LENGTH} > length($body)){
		# неполное сообщение
		return undef;
	}
	(my $cmd = $param->{PATH_INFO}) =~ s|.*/||;

	if($main::params{debug} > 1){
		print "RECV:\n$$rstr\n";
	}

	my $msg = decode_json($body);
	$msg->{cmd} = $cmd;
	$msg->{ip} = $param->{'X-Real-IP'} || $param->{'HTTP_X_REAL_IP'} || $rip;

	return $msg;
}
sub send_http_message {
	my ($fh,$msg) = @_;
	my $status = delete $msg->{status} || 200;
	$msg->{ts} ||=ts();
	$msg->{rid} ||= -1;
	$msg->{type} ||= "response";
	my $body = encode_json($msg);
	my $ret = "HTTP/1.1 ".$status." ".HTTP::Status::status_message($status)."\n";
	$ret .= "Content-Length: ".length($body)."\n";
	$ret .= "Content-Type: text/x-javascript\n";
	$ret .= "\n";
	$ret .= $body;
	my $nw = EV::io $fh, EV::WRITE, \&write_http_handler;
	# создаем слушателя и сохраняем необходимые данные

	$con_all{++$con_count} = $nw;
	$nw->data([$con_count,$ret]);
}
sub write_http_handler {
	my ($w, $r) = @_;
	my $need = length($w->data->[1]);
	if($main::params{debug} > 1){
		print "SEND:\n",$w->data->[1],"\n";
	}
	my $rs;
	my $fh = $w->fh;
	if($fh->opened) {
		$rs = $w->fh->send($w->data->[1]);
	}
	if($rs){
		$need -= $rs;
		# если отправлено не все, то продолжаем ждать
		if($need > 0){
			substr($w->data->[1],0,$rs,"");
			return 0;
		}
	}
	$fh->close;
	delete $con_all{$w->data->[0]};
}

1;
